﻿using Manox.Libs.ODB;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Manox.Libs.MVC
{
    /// <summary>
    /// View manager.
    /// </summary>
    public class ViewsManager : MonoBehaviour
    {
        /// <summary>Objects.</summary>
        public SortedDictionary<Guid, GameObject> Objects;
        /// <summary>Actions.</summary>
        private SortedDictionary<Guid, List<Action<ViewBase>>> Actions;
        /// <summary>Views object.</summary>
        public GameObject ViewsObject;
        /// <summary>ODB manager.</summary>
        private ObjectDatabaseManager _ODBManager;
        /// <summary>ODB manager.</summary>
        public ObjectDatabaseManager ODBManager
        {
            get { return this._ODBManager; }
            set
            {
                if (this._ODBManager != null)
                    this._ODBManager.ItemAdded -= this.OdbItemAdded;

                this._ODBManager = value;

                if (this._ODBManager != null)
                    this.InitializeODB();
            }
        }        

        /// <summary>On view added.</summary>
        public event OnViewAddedCallback OnViewAdded;


        /// <summary>
        /// Constructor.
        /// </summary>
        public ViewsManager()
        {
            this.Objects = new SortedDictionary<Guid, GameObject>();
            this.Actions = new SortedDictionary<Guid, List<Action<ViewBase>>>();
        }

        /// <summary>
        /// On destroy.
        /// </summary>
        public void OnDestroy()
        {
            if (this.ODBManager != null)
                this.ODBManager.ItemAdded -= this.OdbItemAdded;
        }        

        /// <summary>
        /// Initialize ODB.
        /// </summary>
        public void InitializeODB()
        {
            this.Clear();

            this.ODBManager.ForeachAndRegister(this.CreateViewForModel, this.OdbItemAdded);
        }

        /// <summary>
        /// Create view for model.
        /// </summary>
        /// <param name="item"></param>
        private void CreateViewForModel(ObjectDatabaseItem item)
        {
            ViewBase view = null;

            lock (this.Objects)
                if (!this.Objects.ContainsKey(item.Id))
                    if (item is Model)
                    {
                        GameObject viewResource = Resources.Load<GameObject>(
                            (item as Model).ViewResourceName.Get());

                        if (viewResource != null)
                        {
                            GameObject objectInstance = GameObject.Instantiate(viewResource, Vector3.zero, Quaternion.identity) as GameObject;

                            objectInstance.transform.parent = this.ViewsObject.transform;
                            view = objectInstance.GetComponent<ViewBase>();
                            view.SetViewsManager(this);
                            view.SetData(item as Model);

                            this.Objects.Add(item.Id, objectInstance);

                            if (this.OnViewAdded != null)
                                this.OnViewAdded(this, objectInstance);
                        }
                    }

            lock (this.Actions)
                if (this.Actions.ContainsKey(item.Id))
                {
                    foreach (Action<ViewBase> viewAction in this.Actions[item.Id])
                        viewAction(view);

                    this.Actions.Remove(item.Id);
                }
        }

        /// <summary>
        /// Odb item added.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="eventArgs"></param>
        private void OdbItemAdded(object sender, ItemAddedEventArgs eventArgs)
        {
            this.CreateViewForModel(eventArgs.NewItem);
        }

        /// <summary>
        /// Clear.
        /// </summary>
        private void Clear()
        {
            lock (this.Objects)
            {
                foreach (KeyValuePair<Guid, GameObject> viewObject in this.Objects)
                    GameObject.DestroyImmediate(viewObject.Value);

                this.Objects.Clear();
            }

            lock (this.Actions)
                this.Actions.Clear();
        }

        /// <summary>
        /// Get view for model.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public T GetViewForModel<T>(Model model) where T : class
        {
            lock (this.Objects)
            {
                foreach (KeyValuePair<Guid, GameObject> view in this.Objects)
                    if (view.Value.GetComponent<ViewBase>().Data == model)
                        return view.Value.GetComponent<ViewBase>() as T;
            }

            return null;
        }

        /// <summary>
        /// Get view for model.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public void ProcessViewForModel(Model model, Action<ViewBase> action)
        {
            if (model == null)
                return;

            lock (this.Objects)
            {
                ViewBase searchView = null;

                foreach (KeyValuePair<Guid, GameObject> view in this.Objects)
                    if (view.Value.GetComponent<ViewBase>().Data == model)
                        searchView = view.Value.GetComponent<ViewBase>();

                if (searchView != null)
                    action(searchView);
                else
                    lock (this.Actions)
                    {
                        if (!this.Actions.ContainsKey(model.Id))
                            this.Actions.Add(model.Id, new List<Action<ViewBase>>());

                        this.Actions[model.Id].Add(action);
                    }
            }            
        }
    }

    /// <summary>
    /// OnViewAdded callback.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="newView"></param>
    public delegate void OnViewAddedCallback(object sender, GameObject newView);
}
